﻿using System;
using GeoAPI.Geometries;

namespace NetTopologySuite.Geometries
{
	[Serializable]
	public class PrecisionModel : IPrecisionModel, IComparable, IComparable<IPrecisionModel>, IEquatable<IPrecisionModel>
	{
		public static IPrecisionModel MostPrecise(IPrecisionModel pm1, IPrecisionModel pm2)
		{
			if (pm1.CompareTo(pm2) >= 0)
			{
				return pm1;
			}
			return pm2;
		}

		public PrecisionModel()
		{
			this._modelType = PrecisionModels.Floating;
		}

		public PrecisionModel(PrecisionModels modelType)
		{
			this._modelType = modelType;
			if (modelType == PrecisionModels.Fixed)
			{
				this._scale = 1.0;
			}
		}

		[Obsolete("Offsets are no longer supported, since internal representation is rounded floating point")]
		public PrecisionModel(double scale, double offsetX, double offsetY)
		{
			this._modelType = PrecisionModels.Fixed;
			this._scale = scale;
		}

		public PrecisionModel(double scale)
		{
			this._modelType = PrecisionModels.Fixed;
			this._scale = scale;
		}

		public PrecisionModel(PrecisionModel pm)
		{
			this._modelType = pm._modelType;
			this._scale = pm._scale;
		}

		public override int GetHashCode()
		{
			return this._modelType.GetHashCode() ^ this._scale.GetHashCode();
		}

		public bool IsFloating
		{
			get
			{
				return this._modelType == PrecisionModels.Floating || this._modelType == PrecisionModels.FloatingSingle;
			}
		}

		public int MaximumSignificantDigits
		{
			get
			{
				switch (this._modelType)
				{
				case PrecisionModels.Floating:
					return 16;
				case PrecisionModels.FloatingSingle:
					return 6;
				case PrecisionModels.Fixed:
					return 1 + (int)Math.Ceiling(Math.Log(this.Scale) / Math.Log(10.0));
				default:
					throw new ArgumentOutOfRangeException(this._modelType.ToString());
				}
			}
		}

		public double Scale
		{
			get
			{
				return this._scale;
			}
			set
			{
				throw new NotSupportedException();
			}
		}

		public PrecisionModels PrecisionModelType
		{
			get
			{
				return this._modelType;
			}
		}

		[Obsolete("Offsets are no longer used")]
		public double OffsetX
		{
			get
			{
				return 0.0;
			}
		}

		[Obsolete("Offsets are no longer used")]
		public double OffsetY
		{
			get
			{
				return 0.0;
			}
		}

		[Obsolete("Use MakePrecise instead")]
		public void ToInternal(Coordinate cexternal, Coordinate cinternal)
		{
			if (this.IsFloating)
			{
				cinternal.X = cexternal.X;
				cinternal.Y = cexternal.Y;
			}
			else
			{
				cinternal.X = this.MakePrecise(cexternal.X);
				cinternal.Y = this.MakePrecise(cexternal.Y);
			}
			cinternal.Z = cexternal.Z;
		}

		[Obsolete("Use MakePrecise instead")]
		public Coordinate ToInternal(Coordinate cexternal)
		{
			Coordinate coordinate = new Coordinate(cexternal);
			this.MakePrecise(coordinate);
			return coordinate;
		}

		[Obsolete("No longer needed, since internal representation is same as external representation")]
		public Coordinate ToExternal(Coordinate cinternal)
		{
			return new Coordinate(cinternal);
		}

		[Obsolete("No longer needed, since internal representation is same as external representation")]
		public void ToExternal(Coordinate cinternal, Coordinate cexternal)
		{
			cexternal.X = cinternal.X;
			cexternal.Y = cinternal.Y;
		}

		public double MakePrecise(double val)
		{
			if (double.IsNaN(val))
			{
				return val;
			}
			if (this._modelType == PrecisionModels.FloatingSingle)
			{
				return (double)((float)val);
			}
			if (this._modelType == PrecisionModels.Fixed)
			{
				return Math.Floor(val * this._scale + 0.5) / this._scale;
			}
			return val;
		}

		public void MakePrecise(Coordinate coord)
		{
			if (this._modelType == PrecisionModels.Floating)
			{
				return;
			}
			coord.X = this.MakePrecise(coord.X);
			coord.Y = this.MakePrecise(coord.Y);
		}

		public override string ToString()
		{
			string result = "UNKNOWN";
			if (this._modelType == PrecisionModels.Floating)
			{
				result = "Floating";
			}
			else if (this._modelType == PrecisionModels.FloatingSingle)
			{
				result = "Floating-Single";
			}
			else if (this._modelType == PrecisionModels.Fixed)
			{
				result = "Fixed (Scale=" + this.Scale + ")";
			}
			return result;
		}

		public override bool Equals(object other)
		{
			return other != null && other is IPrecisionModel && this.Equals((IPrecisionModel)other);
		}

		public bool Equals(IPrecisionModel otherPrecisionModel)
		{
			return this._modelType == otherPrecisionModel.PrecisionModelType && this._scale == otherPrecisionModel.Scale;
		}

		public static bool operator ==(PrecisionModel obj1, PrecisionModel obj2)
		{
			return object.Equals(obj1, obj2);
		}

		public static bool operator !=(PrecisionModel obj1, PrecisionModel obj2)
		{
			return !(obj1 == obj2);
		}

		public int CompareTo(object o)
		{
			return this.CompareTo((IPrecisionModel)o);
		}

		public int CompareTo(IPrecisionModel other)
		{
			int maximumSignificantDigits = this.MaximumSignificantDigits;
			int maximumSignificantDigits2 = other.MaximumSignificantDigits;
			return maximumSignificantDigits.CompareTo(maximumSignificantDigits2);
		}

		private const int FloatingPrecisionDigits = 16;

		private const int FloatingSinglePrecisionDigits = 6;

		private const int FixedPrecisionDigits = 1;

		public const double MaximumPreciseValue = 9007199254740992.0;

		private readonly PrecisionModels _modelType;

		private readonly double _scale;
	}
}
